package weka.learning.semisupervised;
/**
 * Implementação do método semi-supervisionado Self-Training.
 * Segue comentado com devidas observações; 
 */

import java.util.Random;

import weka.classifiers.Classifier;
import weka.classifiers.Evaluation;
import weka.core.Instances;

public class SelfTraining {
	
    private Instances setL; 			//Sub-conjunto de dados rotulados
    private Instances setU; 			//Sub-conjunto de dados n�o-rotulados
    private Classifier classifierH;             //Classificador treinado a partir do cojunto L
    private Instances subSetU; 			//Sub-conjunto U' que ir� conter todos os exemplos de U rotulados por H
    private String informaçãodeSaida;
	
    public String Executar(Instances setOriginal, Classifier algoClassificacao) throws Exception{
    	
    	/** Iniciando Algoritmo do Self-Training */
    	
    	// Separa o conjunto de teste em L (rotulados) e U (n�o-rotulados)
    	separaConjunto(setOriginal);
    	// Imprime cada sub-conjunto (L e U)
    	informaçãodeSaida += setL.toString()+"\n";
    	informaçãodeSaida += setU.toString();
    	
    	/** Condi��o enquanto ainda tiver elementos no conjunto de U */
    	while(setU.numInstances() > 0) {
	    	criaClassificadorH(algoClassificacao);                                  // passando o algoritmo de classifica��o
	    	classificaU(); 								// gerando U' a partir do classificador H
	    	informaçãodeSaida += subSetU.toString();
	    	melhoresExemplos();							// seleciona as melhores int�ncias de U' e as adiciona em L
    	} /** Fim do while..*/
    	
    	informaçãodeSaida += "\n \n RESULTADO FINAL	\n";
    	informaçãodeSaida += setL.toString() + "\n";
    	
    	return informaçãodeSaida;
    }
	
	/**  Separa um conjunto de instancias em dois sub-conjuntos:
	 *   Sub-conjunto L: instancias rotuladas
	 *   Sub-conjunto U: instancias n�o rotuladas
	 *  
	 *  @param setOriginal Instances - conjunto de dados original
	 */
	public void separaConjunto(Instances setOriginal) {
        
		//Cria o sub-conjunto L
		setL = new Instances(setOriginal); //cria uma c�pia do conjunto original                 
        setL.setRelationName("Set L");     //nomeia o sub-conjunto L
        
        //Cria o sub-conjunto U         
		setU = new Instances(setOriginal); //cria uma c�pia do conjunto original          
		setU.setRelationName("Set U"); 	   //nomeia o sub-conjunto U
		
        //Percorre todo o sub-conjunto L para eliminar todos os elementos sem-r�tulos
        for( int i = 0; i < setL.numInstances(); i++ ) {
			//verifica se o valor do atributo classe � null
			if( setL.instance(i).isMissing(setL.numAttributes() - 1)){ 
				setL.delete(i);
				i--;       		 
			}       	 
        }
        
        //Percorre todo o sub-conjunto U para eliminar todos os elementos com r�tulos         
        for(int i=0; i< setU.numInstances();i++) {
        	//verifica se o valor do atributo classe � diferente de null
	    	if( !setU.instance(i).isMissing(setU.numAttributes() - 1) ) {
	       		setU.delete(i);
	       		i--;       		 
	   	  	}
        }
	}

	/** Gera o classificador H com base no conjunto dos dados rotulados
	 *  
	 *   @param  algClassificacao Classifier - o algoritmo de classifica��o definido pelo usu�rio
	 *   
	 *   @return Evaluation - avalia��o feita do classificador
	 */
	public void criaClassificadorH(Classifier algClassificacao){
		
		classifierH = algClassificacao;
		Evaluation eTest = null;
		
		try {
			classifierH.buildClassifier(setL); // utilizando o conjunto L para treinamento
	        eTest = new Evaluation(setL);
	        eTest.evaluateModel(classifierH, setL); // testando o modelo gerado a partir do treinamento
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
                 
		imprimeResultadoH(eTest);
	}

	/** Classifica o conjunto de dados n�o-rotulados com base no classificador H
	 * 
	 * @throws Exception 
	 * 
	 */
	public void classificaU() throws Exception {
		
		subSetU = new Instances(setU); // O sub conjunto U' tem todos os elementos do conjunto U
        //Renomeando o sub conjunto U'
		subSetU.setRelationName("Sub set U'");              
		//Definindo o atributo classe.
		subSetU.setClassIndex(subSetU.numAttributes() - 1); 
        
        //Classificando com H
        for(int i=0;i < subSetU.numInstances();i++){
        	double classe = classifierH.classifyInstance(subSetU.instance(i));
	        String valor = subSetU.instance(i).attribute(subSetU.numAttributes() - 1).value((int) classe);
	        subSetU.instance(i).setValue(subSetU.numAttributes() - 1,valor);
        }		
	}
	
	/** Escolhe as melhores int�ncias dos exemplos que foram rotulados (U') 
	 * 	para serem adicionados ao conjunto de exemplos rotulados (L)
	 *  
	 */
	public void melhoresExemplos() {
		
		int n_10p = 0; // quantidade de inst�ncias que ser�o adicionadas ao cojunto L
		//Caso o numero de inst�ncias em U for maior que 10 ent�o capture 10% dessas inst�ncias 
		if(subSetU.numInstances() > 10)
		     n_10p = (subSetU.numInstances()*10)/100; 
		else
			 n_10p = subSetU.numInstances();

		while(n_10p > 0) {
			//criando um  random para escolher aleatoriamente uma inst�ncia do cojunto setU
			Random random = new Random();
			int index = random.nextInt(subSetU.numInstances());
			
			// Capturando inst�ncia sorteada do subconjunto U' e adicionando em L.
			setL.add(subSetU.instance(index));
			// Apagando inst�ncia do conjunto U e do subconjunto U'
			setU.delete(index);
			subSetU.delete(index);
				 	
			 //Repetir processo at� na restar nada em n_10p
			 n_10p--;
		}
	}

	/**
	 * M�todo para imprimir informa��es do classificador e matriz de confus�o.
	 * 
	 * @param test Evaluation - Avalia��o disponivel depois de gerar o classificador.
	 */
	private void imprimeResultadoH(Evaluation test){
		String strSummary = test.toSummaryString();
		informaçãodeSaida += " \n"+strSummary;
	
	    // Get the confusion matrix
	    double[][] cmMatrix = test.confusionMatrix();
	    for(int row_i=0; row_i<cmMatrix.length; row_i++){
	        for(int col_i=0; col_i<cmMatrix.length; col_i++){
	        	informaçãodeSaida += cmMatrix[row_i][col_i];
	        	informaçãodeSaida += "|";
	        }
	        informaçãodeSaida += "\n";
	    }
	}
	
	
	/**
	 * @return the setL
	 */
	public Instances getSetL() {
		return setL;
	}

	/**
	 * @param setL the setL to set
	 */
	public void setSetL(Instances setL) {
		this.setL = setL;
	}

	/**
	 * @return the setU
	 */
	public Instances getSetU() {
		return setU;
	}

	/**
	 * @param setU the setU to set
	 */
	public void setSetU(Instances setU) {
		this.setU = setU;
	}

	/**
	 * @return the classifierH
	 */
	public Classifier getClassifierH() {
		return classifierH;
	}

	/**
	 * @param classifierH the classifierH to set
	 */
	public void setClassifierH(Classifier classifierH) {
		this.classifierH = classifierH;
	}

	/**
	 * @return the subSetU
	 */
	public Instances getSubSetU() {
		return subSetU;
	}

	/**
	 * @param subSetU the subSetU to set
	 */
	public void setSubSetU(Instances subSetU) {
		this.subSetU = subSetU;
	}
}
